package com.candy.biz.service.impl;


import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.CircleCaptcha;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import com.candy.biz.domain.entity.SysUserRole;
import com.candy.biz.domain.param.*;
import com.candy.biz.domain.vo.SysUserLoginVO;
import com.candy.biz.domain.vo.SysUserVO;
import com.candy.biz.enums.ConfigKeyEnum;
import com.candy.biz.enums.StateEnum;
import com.candy.biz.enums.UserTypeEnum;
import com.candy.biz.manager.SysConfigManager;
import com.candy.biz.manager.SysUserManager;
import com.candy.biz.mapper.SysUserMapper;
import com.candy.biz.mapper.SysUserRoleMapper;
import com.candy.biz.service.SysUserService;
import com.candy.common.constant.CommonConstant;
import com.candy.common.domain.PageVO;
import com.candy.common.enums.AdminErrorEnum;
import com.candy.common.enums.LoginDeviceTypeEnum;
import com.candy.common.enums.RedisKeyEnum;
import com.candy.common.exception.BizException;
import com.candy.common.manager.RedisManager;
import com.candy.common.utils.Assert;
import com.candy.common.utils.CommonUtil;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.If;
import com.mybatisflex.core.query.QueryChain;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import com.candy.biz.domain.entity.SysUser;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import static com.candy.biz.domain.entity.table.SysUserRoleTableDef.SYS_USER_ROLE;
import static com.candy.biz.domain.entity.table.SysUserTableDef.SYS_USER;


/**
 * 系统用户表 服务层实现。
 *
 * @author rong xi
 * @since 1.0.0
 */
@Service
@Slf4j
@AllArgsConstructor
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {
    private final SysUserRoleMapper sysUserRoleMapper;

    /**
     * 用户详情
     * @param id 用户id
     * @return 详情对象
     */
    @Override
    public SysUserVO queryInfo(Long id) {
        return this.getByIdOpt(id).map(SysUserManager::poToVo).orElse(null);
    }

    /**
     * 分页查询
     * @param param 查询参数
     * @return 分页结果
     */
    @Override
    public PageVO<SysUserVO> queryPage(SysUserQueryParam param) {
        Page<SysUser> page = queryChain()
                .select(SYS_USER.ALL_COLUMNS)
                .from(SYS_USER)
                .where(SYS_USER.LOGIN_ACCOUNT.like(param.getUserAccount(), If::hasText))
                .and(SYS_USER.USER_NAME.like(param.getUserName(), If::hasText))
                .and(SYS_USER.STATE.eq(param.getState(), If::notNull))
                .and(SYS_USER.DEPT_ID.eq(param.getDeptId(), If::notNull))
                .orderBy(param.getOrderBy(SYS_USER))
                .page(param.getPage());
                return SysUserManager.pageToPageVO(page);
    }

    /**
     * 保存变更
     * @param param 变更参数
     * @return 影响条数
     */
    @Override
    public Boolean saveHandle(SysUserSaveParam param) {
        return CommonUtil.<SysUserSaveParam,Boolean>predicateFunction(param,
                p->ObjectUtil.isNull(p.getId()),
                (p)->this.save(SysUserManager.insertPrepare(p)),
                (p)-> {
                    RedisManager.delete(RedisKeyEnum.USER_INFO.getKey(p.getId()));
                    return this.updateById(SysUserManager.updatePrepare(p));
                }
        );
    }

    /**
     * 根据条件分页查询数据并导出。
     *
     * @param param 查询对象
     */
    @Override
    @SneakyThrows
    public void export(SysUserQueryParam param) {
        //修改分页条件
        param.preparationExport();
        //查询数据
        List<SysUserVO> rows = queryPage(param).getList();
        //导出文件
        CommonUtil.exportExcel("系统用户",rows,SysUserVO.class);
    }


    /**
     * 重置密码
     * @param param 密码参数
     * @return 重置结果
     */
    @Override
    public Boolean resetPassword(SysUserResetPasswordParam param) {
        queryChain()
                .select(SYS_USER.ID,SYS_USER.SALT)
                .from(SYS_USER)
                .where(SYS_USER.ID.eq(param.getId()))
                .limit(1)
                .oneOpt()
                .ifPresent(u->{
                    u.setLoginPassword(SysUserManager.genPassword(param.getLoginPassword(),u.getSalt()));
                    updateById(u);
                });
        return true;
    }


    /**
     * 更新用户状态
     *
     * @param param 状态参数
     * @return 更新结果
     */
    @Override
    public Boolean updateState(SysUserUpdateStateParam param) {
        updateChain()
                .set(SysUser::getState,param.getState())
                .where(SysUser::getId).eq(param.getId())
                .update();
        CommonUtil.predicateConsumer(param,
                (p)->StateEnum.DISABLE.getValue().equals(p.getState()),
                //踢掉用户
                (p)->StpUtil.kickout(p.getId())
        );
        RedisManager.delete(RedisKeyEnum.USER_INFO.getKey(param.getId()));
        return true;
    }

    /**
     * 登录
     *
     * @param param 登录参数
     * @return 登录结果
     */
    @Override
    public SysUserLoginVO login(SysUserLoginParam param) {
        //判断是否需要校验验证码
        String enableCaptcha = SysConfigManager.getConfigValueOpt(ConfigKeyEnum.ENABLE_CAPTCHA.getKey()).orElse(CommonConstant.NO.toString());
        //1.校验验证码是否必填
        CommonUtil.predicateConsumer(param,
                (p)->CommonConstant.YES.toString().equals(enableCaptcha),
                (p)-> {
                    Assert.notBlank(p.getCaptchaCode(), AdminErrorEnum.CAPTCHA_REQUIRE_ERROR);
                    Assert.notBlank(p.getClientId(), AdminErrorEnum.CLIENT_ID_REQUIRE_ERROR);
                    //校验验证码
                    String cacheCaptcha = RedisManager.<String>getValueOpt(RedisKeyEnum.CAPTCHA.getKey(p.getClientId())).orElse("");
                    Assert.equals(cacheCaptcha, p.getCaptchaCode(),AdminErrorEnum.CAPTCHA_ERROR);
                }
        );

        SysUser sysUser = this.getMapper().selectOneByCondition(SYS_USER.LOGIN_ACCOUNT.eq(param.getLoginAccount()));
        //2.判断用户是否存在
        Assert.notNull(sysUser, AdminErrorEnum.LOGIN_ERROR);
        //3.判断用户是否被禁用
        Assert.equals(sysUser.getState(), CommonConstant.NO,AdminErrorEnum.USER_DISABLED);
        //4.判断密码是否对
        String password = SysUserManager.genPassword(param.getLoginPassword(), sysUser.getSalt());
        CommonUtil.predicateConsumer(password,
                (p)->!p.equals(sysUser.getLoginPassword()),
                (p)->{
                    //5.判断是否超过错误次数,如果超过则禁用改用户
                    Integer configFailNum = Integer.valueOf(SysConfigManager.getConfigValueOpt(ConfigKeyEnum.LOGIN_ERROR_DISABLE_TIME.getKey()).orElse("0"));
                    Integer hasLoginFailNum = RedisManager.<Integer>getValueOpt(RedisKeyEnum.LOGIN_FAIL_NUM.getKey(sysUser.getId().toString())).orElse(0);
                    CommonUtil.predicateConsumer(configFailNum,
                            //失败次数不为空,失败次数大于0，已失败次数>=配置失败次数
                            (n)->ObjectUtil.isNotNull(n) && n > 0 && hasLoginFailNum >= configFailNum,
                            (n)->{
                               //禁用用户
                                SysUserUpdateStateParam stateParam = new SysUserUpdateStateParam();
                                stateParam.setId(sysUser.getId());
                                stateParam.setState(StateEnum.DISABLE.getValue());
                                this.updateState(stateParam);
                            },
                            (n)->{
                                //登录失败次数+1
                                RedisManager.incr(RedisKeyEnum.LOGIN_FAIL_NUM.getKey(sysUser.getId().toString()),1);
                                RedisManager.expire(RedisKeyEnum.LOGIN_FAIL_NUM.getKey(sysUser.getId().toString()),RedisKeyEnum.LOGIN_FAIL_NUM.getTimeout());
                            }
                    );
                    throw BizException.create(AdminErrorEnum.LOGIN_ERROR);
                }
        );

        //6.用户登录
        StpUtil.login(sysUser.getId(), LoginDeviceTypeEnum.PC.name());
        //7.获取Token相关参数
        SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
        //8.组装返回结果
        return SysUserLoginVO.builder()
                .user(SysUserManager.poToVo(sysUser))
                .token(tokenInfo)
                .build();
    }

    /**
     * 退出登录
     *
     * @return 退出登录结果
     */
    @Override
    public Boolean loginOut() {
        StpUtil.logout();
        return true;
    }

    /**
     * 分配角色
     *
     * @param param 角色参数
     * @return 分配结果
     */
    @Override
    @Transactional
    public Boolean assigningRoles(SysUserAssigningRolesParam param) {
        LocalDateTime now = LocalDateTime.now();
        // 1.删除旧数据
        sysUserRoleMapper.deleteByCondition(SYS_USER_ROLE.USER_ID.eq(param.getUserId()));
        //2.处理新数据
        Optional.ofNullable(param.getRoleIds())
                .filter(CollectionUtil::isNotEmpty)
                .map(l->l.stream()
                        .map(rid->SysUserRole.builder()
                                .id(IdUtil.getSnowflakeNextId())
                                .roleId(rid)
                                .userId(param.getUserId())
                                .createTime(now)
                                .createUser(StpUtil.getLoginIdAsLong())
                                .build()).collect(Collectors.toList()))
                .ifPresent(sysUserRoleMapper::insertBatch);
        return true;
    }

    /**
     * 查询用户已分配的角色ids
     *
     * @param id 用户id
     * @return 角色id列表
     */
    @Override
    public List<Long> queryAssignedRole(Long id) {
       return QueryChain.of(sysUserRoleMapper)
                .select(SYS_USER_ROLE.ROLE_ID)
                .from(SYS_USER_ROLE)
                .where(SYS_USER_ROLE.USER_ID.eq(id))
                .listAs(Long.class);
    }

    /**
     * 获取验证码图片
     *
     * @param clientId 客户端id
     */
    @Override
    public void getCaptchaImg(String clientId) {
        CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(200, 80, 4, 20);
        RedisManager.setValue(RedisKeyEnum.CAPTCHA.getKey(clientId),captcha.getCode(),RedisKeyEnum.CAPTCHA.getTimeout());
        RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
        Optional.ofNullable(((ServletRequestAttributes)requestAttributes).getResponse())
                .ifPresent(response->{
                    try {
                        captcha.write(response.getOutputStream());
                    } catch (IOException e) {
                        log.error("输出验证码图片异常:",e);
                        throw new RuntimeException(e);
                    }
                });
    }

    /**
     * 删除用户
     *
     * @param id 用户id
     * @return 删除结果
     */
    @Override
    public Boolean removeUser(Long id) {
        SysUser sysUser = this.getById(id);
        Assert.notNull(sysUser,AdminErrorEnum.USER_NOT_EXISTS);
        Assert.notEquals(UserTypeEnum.SYSTEM.getValue(),sysUser.getType(),AdminErrorEnum.SYSTEM_USER_CANT_NOT_DELETE);
        //清除缓存
        RedisManager.delete(RedisKeyEnum.USER_INFO.getKey(id));
        //删除用户
        return removeById(id);
    }

}
